package com.yyfax.pay.common.util;

import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.transform.sax.SAXSource;

import org.apache.commons.lang3.StringUtils;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLFilterImpl;
import org.xml.sax.helpers.XMLReaderFactory;

import com.alibaba.fastjson.JSON;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.QNameMap;
import com.thoughtworks.xstream.io.xml.StaxDriver;
import com.thoughtworks.xstream.io.xml.StaxWriter;
import com.yyfax.framework.common.Log;

public class XmlUtil {

	private static Logger logger = LoggerFactory.getLogger(XmlUtil.class);

	public static String toXML(Object obj) {
		try {
			JAXBContext context = JAXBContext.newInstance(obj.getClass());

			Marshaller marshaller = context.createMarshaller();
			marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");// //编码格式
			// marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);// 是否格式化生成的xml串
			marshaller.setProperty(Marshaller.JAXB_FRAGMENT, false);// 是否省略xm头声明信息

			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			XmlRootElement root = obj.getClass().getAnnotation(XmlRootElement.class);
			if (null != root && !root.namespace().equals("##default")) {
				OutputFormat format = new OutputFormat();
				// format.setIndent(true);
				// format.setNewlines(true);
				format.setNewLineAfterDeclaration(false);
				XMLWriter writer = new XMLWriter(baos, format);

				XMLFilterImpl nsfFilter = new XMLFilterImpl() {
					private boolean ignoreNamespace = false;
					private String rootNamespace = null;
					private boolean isRootElement = true;

					@Override
					public void startDocument() throws SAXException {
						super.startDocument();
					}

					@Override
					public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
						if (this.ignoreNamespace)
							uri = "";
						if (this.isRootElement)
							this.isRootElement = false;
						else if (!uri.equals("") && !localName.contains("xmlns"))
							localName = localName + " xmlns=\"" + uri + "\"";

						super.startElement(uri, localName, localName, atts);
					}

					@Override
					public void endElement(String uri, String localName, String qName) throws SAXException {
						if (this.ignoreNamespace)
							uri = "";
						super.endElement(uri, localName, localName);
					}

					@Override
					public void startPrefixMapping(String prefix, String url) throws SAXException {
						if (this.rootNamespace != null)
							url = this.rootNamespace;
						if (!this.ignoreNamespace)
							super.startPrefixMapping("", url);

					}
				};
				nsfFilter.setContentHandler(writer);
				marshaller.marshal(obj, nsfFilter);
			} else {
				marshaller.marshal(obj, baos);
			}

			return new String(baos.toByteArray(), "utf-8");

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static <T> T fromXML(String xml, Class<T> valueType) {
		try {
			JAXBContext context = JAXBContext.newInstance(valueType);
			Unmarshaller unmarshaller = context.createUnmarshaller();
			XMLReader reader = XMLReaderFactory.createXMLReader();
			XMLFilterImpl nsfFilter = new XMLFilterImpl() {
				private boolean ignoreNamespace = false;

				@Override
				public void startDocument() throws SAXException {
					super.startDocument();
				}

				@Override
				public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
					if (this.ignoreNamespace)
						uri = "";
					super.startElement(uri, localName, qName, atts);
				}

				@Override
				public void endElement(String uri, String localName, String qName) throws SAXException {
					if (this.ignoreNamespace)
						uri = "";
					super.endElement(uri, localName, localName);
				}

				@Override
				public void startPrefixMapping(String prefix, String url) throws SAXException {
					if (!this.ignoreNamespace)
						super.startPrefixMapping("", url);
				}
			};
			nsfFilter.setParent(reader);
			InputSource input = new InputSource(new StringReader(xml));
			SAXSource source = new SAXSource(nsfFilter, input);
			// SAXSource source = new SAXSource(input);
			return (T) unmarshaller.unmarshal(source);
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}
	}

	/**
	 * 将字符串转换成xml对象
	 * 
	 * @author heizq
	 * @date 2017-10-03 10:16:04
	 * @since v1.0.0
	 * @param xml xml字符串
	 * @param rootAlias 根节点名称
	 * @param valueType 转成的对象class
	 * @param fieldName 内嵌子节点名称
	 * @param subClass 内嵌子节点对象class
	 * @return
	 */
	public static <T> T fromXMLForXStream(String xml, String rootAlias, Class<T> valueType, String fieldName, Class<?> subClass) {
		final String op = "XmlUtil.fromXMLForXStream";
		QNameMap qmap = new QNameMap();
		// qmap.setDefaultNamespace("http://www.99bill.com/mas_cnp_merchant_interface");
		qmap.setDefaultPrefix("");
		StaxDriver staxDriver = new StaxDriver(qmap);
		XStream xstream = new XStream(staxDriver);
		xstream.autodetectAnnotations(true);
		xstream.ignoreUnknownElements();
		if (StringUtils.isNotBlank(fieldName) && subClass != null) {
			try {
				xstream.addDefaultImplementation(subClass, valueType.getDeclaredField(fieldName).getType());
			} catch (NoSuchFieldException e) {
				logger.error(Log.op(op).msg("xml转对象失败").kv("xmlStr", xml).toString(), e);
			}
			xstream.alias(fieldName, subClass);
			xstream.processAnnotations(subClass);
		}
		xstream.alias(rootAlias, valueType);
		return (T) xstream.fromXML(xml);
	}

	/**
	 * 对象转xml
	 * 
	 * @author heizq
	 * @date 2017-09-30 14:58:31
	 * @since v1.0.0
	 * @param obj
	 * @param rootAlias 根节点别名
	 * @return
	 */
	public static <T> String toXmlForXStream(T obj, String rootAlias) {
		final String op = "XmlUtil.toXmlForXStream";
		QNameMap qmap = new QNameMap();
		qmap.setDefaultPrefix("");
		StaxDriver staxDriver = new StaxDriver(qmap);
		XStream xstream = new XStream(staxDriver);
		xstream.autodetectAnnotations(true);
		xstream.ignoreUnknownElements();
		xstream.alias(rootAlias, obj.getClass());
		StaxWriter sw = null;
		try (StringWriter strWriter = new StringWriter()) {
			sw = new StaxWriter(staxDriver.getQnameMap(), staxDriver.getOutputFactory().createXMLStreamWriter(strWriter), false, true);
			xstream.marshal(obj, sw);
			sw.flush();
			return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + strWriter.toString().replaceAll("__", "_");
		} catch (Exception e) {
			logger.error(Log.op(op).msg("对象转xml失败！").kv("obj", JSON.toJSON(obj)).toString(), e);
		} finally {
			if (sw != null) {
				sw.close();
			}
		}
		return null;
	}

	/**
	 * 对象转xml, 不包含<?xml version="1.0" encoding="UTF-8"?>
	 *
	 * @author yejc
	 * @date 2017-10-12
	 * @since v2.0.0
	 * @param obj
	 * @param rootAlias
	 * @param <T>
	 * @return
	 */
	public static <T> String toXmlWithoutPrefix(T obj, String rootAlias) {
		final String op = "XmlUtil.toXmlWithoutPrefix";
		QNameMap qmap = new QNameMap();
		qmap.setDefaultPrefix("");
		StaxDriver staxDriver = new StaxDriver(qmap);
		XStream xstream = new XStream(staxDriver);
		xstream.autodetectAnnotations(true);
		xstream.ignoreUnknownElements();
		xstream.alias(rootAlias, obj.getClass());
		StaxWriter sw = null;
		try (StringWriter strWriter = new StringWriter()) {
			sw = new StaxWriter(staxDriver.getQnameMap(), staxDriver.getOutputFactory().createXMLStreamWriter(strWriter), false, true);
			xstream.marshal(obj, sw);
			sw.flush();
			return strWriter.toString().replaceAll("__", "_");
		} catch (Exception e) {
			logger.error(Log.op(op).msg("对象转xml失败！").kv("obj", JSON.toJSON(obj)).toString(), e);
		} finally {
			if (sw != null) {
				sw.close();
			}
		}
		return null;
	}

	/**
	 * xml字符串转换成实体对象
	 * 
	 * @author buyi
	 * @date 2018-01-05 10:35:39
	 * @since v2.1.5
	 * @param xml
	 * @param t
	 * @return
	 */
	public static <T> T fromXMLForXStream(String xml, Class<?>... clazz) {
		XStream xstream = new XStream(new DomDriver());
		xstream.ignoreUnknownElements();

		xstream.processAnnotations(clazz);
		return (T) xstream.fromXML(xml);
	}
}
